AWS Amplify+Angular6+Cognitoでログインページを作ってみる ~フロントエンド編①~
どうも!大阪オフィスの西村祐二です。
本ブログは下記の続きになります。
- AWS Amplify+Angular6+Cognitoでログインページを作ってみる ~バックエンド編~
- 今回:AWS Amplify+Angular6+Cognitoでログインページを作ってみる ~フロントエンド編①~
- AWS Amplify+Angular6+Cognitoでログインページを作ってみる ~フロントエンド編②~
- AWS Amplify+Angular6+Cognitoでログインページを作ってみる ~UI編~
ゴールとして下記動画のようなサイトを構築していきます。
いよいよですが、Angular6とAWS-Amplify
を使ってフロントエンド部分をやっていきます。今回はフロントエンド部分の設定周りと、外部と通信するところのロジック部分の実装になります。
基本的な実装部分やAWS-Amplify
の実装部分は下記ブログを参考にさせていただきました。m(_ _)m
AWS Amplify + AngularでサーバーレスSPAの認証をするサンプル
Angularでログインページ作成
環境
- Angular
Angular CLI: 6.0.8 Node: 8.11.3 OS: darwin x64 Angular: 6.0.5 ... animations, common, compiler, compiler-cli, core, forms ... http, language-service, platform-browser ... platform-browser-dynamic, router Package Version ----------------------------------------------------------- @angular-devkit/architect 0.6.8 @angular-devkit/build-angular 0.6.8 @angular-devkit/build-optimizer 0.6.8 @angular-devkit/core 0.6.8 @angular-devkit/schematics 0.6.8 @angular/cli 6.0.8 @ngtools/webpack 6.0.8 @schematics/angular 0.6.8 @schematics/update 0.6.8 rxjs 6.2.1 typescript 2.7.2 webpack 4.8.3
- AWS Amplify: 0.4.4
-
Bootstrap: 4.1.1
Angular CLIインストール
AngularのCLIをインストールします。
$ npm install -g @angular/cli@latest
雛形作成
CLIを使って、雛形を作成します。
$ ng new test-amplify --routing $ cd test-amplify
パッケージインストール
今回使うパッケージをインストールします。
- AWS-Amplify
$ npm install --save aws-amplify
- Bootstrap4
$ npm install --save bootstrap && npm install --save jquery popper.js
環境設定
▼aws-sdkを使うためにnodeを定義します。
{ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", "module": "es2015", "types": [ "node" ] }, "exclude": [ "src/test.ts", "**/*.spec.ts" ] }
▼Cognitoの設定、API Gatewayのエンドポイントの情報を設定ファイルに記述します。
export const environment = { production: false, amplify: { // AWS Amplify(Auth)の設定 Auth: { region: 'ap-northeast-1', userPoolId: 'ap-northeast-1_xxxxxxxxxx', userPoolWebClientId: 'xxxxxxxxxxxxxxxxx' } }, // API Gatewayのエンドポイントの設定 apiBaseUrl: 'https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/stg', // Localstorageの設定 localstorageBaseKey: 'CognitoIdentityServiceProvider.<userPoolWebClientIdの値>.' };
今回、特にCognito IDプールは使わないので、設定していません。AWS-Amplify
のAPIクラスを利用する場合はCognito IDプールを設定する必要があるので注意ください。今回はAWS-Amplify
のAPIクラスは利用せず、AngularのRxJSのメソッドを使ってHTTPリクエストなどを行っていきます。
ログイン後にトークンやユーザ情報をローカルストレージから取得するため、ローカルストレージの設定を行っています。ログイン時に保存されるキーの値として、CognitoIdentityServiceProvider.<userPoolWebClientIdの値>.<属性>
として保存されています。
ちなみに、ローカルストレージの確認方法として、Chromeの場合はデベロッパーツールのApplicationから確認できます。
▼UIにBootstrap4を利用するので、ファイルを読み込みます。
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "test-amplify": { "root": "", "sourceRoot": "src", "projectType": "application", "prefix": "app", "schematics": {}, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/test-amplify", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "src/styles.css", "node_modules/bootstrap/dist/css/bootstrap.min.css" ], "scripts": [ "node_modules/jquery/dist/jquery.slim.min.js", "node_modules/popper.js/dist/umd/popper.min.js", "node_modules/bootstrap/dist/js/bootstrap.min.js" ] }, "configurations": { "production": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "test-amplify:build" }, "configurations": { "production": { "browserTarget": "test-amplify:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "test-amplify:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.spec.json", "karmaConfig": "src/karma.conf.js", "styles": [ "src/styles.css", "node_modules/bootstrap/dist/css/bootstrap.min.css" ], "scripts": [ "node_modules/jquery/dist/jquery.slim.min.js", "node_modules/popper.js/dist/umd/popper.min.js", "node_modules/bootstrap/dist/js/bootstrap.min.js" ], "assets": [ "src/favicon.ico", "src/assets" ] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "src/tsconfig.app.json", "src/tsconfig.spec.json" ], "exclude": [ "**/node_modules/**" ] } } } }, "test-amplify-e2e": { "root": "e2e/", "projectType": "application", "architect": { "e2e": { "builder": "@angular-devkit/build-angular:protractor", "options": { "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "test-amplify:serve" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": "e2e/tsconfig.e2e.json", "exclude": [ "**/node_modules/**" ] } } } } }, "defaultProject": "test-amplify" }
AuthService
Amazon Cognitoと通信する認証系の処理をまとめたサービスを作成します。
$ ng g service auth/auth
コンストラクタでAmplify.configure()
に設定ファイルを渡し初期化しています。
AWS Amplify
のAuthモジュールの機能をハンドリングするメソッドを実装していきます。
import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { Observable, BehaviorSubject, from, of } from 'rxjs'; import { map, tap, catchError } from 'rxjs/operators'; import Amplify, { Auth } from 'aws-amplify'; import { environment } from './../../environments/environment'; @Injectable({ providedIn: 'root' }) export class AuthService { loggedIn: BehaviorSubject<boolean>; password: String; constructor(private router: Router) { Amplify.configure(environment.amplify); this.loggedIn = new BehaviorSubject<boolean>(false); } /** サインアップ */ public signUp(email, password): Observable<any> { this.password = password; return from(Auth.signUp(email, password, email)); } /** 検証 */ public confirmSignUp(email, code): Observable<any> { return from(Auth.confirmSignUp(email, code)); } /** ログイン */ public signIn(email, password): Observable<any> { return from(Auth.signIn(email, password)).pipe( tap(() => this.loggedIn.next(true)) ); } /** ログインユーザ情報の取得 */ public getData(): Observable<any> { return from(Auth.currentAuthenticatedUser()); } /** idtokenを取得 */ public getIdToken(): string { return Auth.currentSession()['__zone_symbol__value']['idToken']['jwtToken']; } /** ログイン状態の取得 */ public isAuthenticated(): Observable<boolean> { return from(Auth.currentAuthenticatedUser()).pipe( map(result => { this.loggedIn.next(true); return true; }), catchError(error => { this.loggedIn.next(false); return of(false); }) ); } /** ログアウト */ public signOut() { from(Auth.signOut()).subscribe( result => { this.loggedIn.next(false); this.router.navigate(['/login']); }, error => console.log(error) ); } }
- loggedIn(BehaviorSubject)
- グローバルなナビゲーションの表示を切り替えるために使っています。つまり、ログイン状態だったら、ナビバーのログイン画面へのリンクを非表示にしたり制御するための設定です。
- password: String
- サインアップが完了したあと、ログインした後のページに遷移させたいために、キャッシュするようにしています。他に良い方法があれば教えていただけると助かります。
- signUp()、confirmSignUp()、signIn()、getData()
AWS Amplify
のAuthモジュールのメソッドが返すPromiseをRxJSのfrom(旧fromPromise)でPromiseからObservableに変換しています。
- getIdToken()
- API GatewayにはCognito認証をかけており、APIをリクエストするためにはログイン時に発行されるjwtトークンが必要になります。そのトークンを取得するメソッドになります。取得するために、
Auth.currentAuthenticatedUser()
からString型でトークンだけを返すようにしています。
- API GatewayにはCognito認証をかけており、APIをリクエストするためにはログイン時に発行されるjwtトークンが必要になります。そのトークンを取得するメソッドになります。取得するために、
- isAuthenticated()
- ログイン状態にある場合には
Auth.currentAuthenticatedUser()
がPromiseのユーザー情報オブジェクトを返すため、それをBoolean
のObservableに変換しています。後ほど実装するGuardで使うメソッドではTrue
かfalse
のBoolean
で返す必要があるためです。
- ログイン状態にある場合には
- signOut()
Auth.signOut()
を呼びつつ、ログイン画面に遷移させます。
AuthGuard
AngularのGuardでは、ルーティング前にセッションチェックなどの処理を行って、未ログインの場合、ログインページにルーティングするなどの制御を行うことができます。
$ ng g guard auth/auth
今回は、CanActivate
の機能を使って、ログイン状態にない場合に、login画面に画面遷移させるGuardを作成します。
CanActivate
はPromiseまたは、Observableの非同期処理を行った結果のbooleanを戻り値に取ることが出来るので、
先ほどのAuthService
で作成したisAuthenticated()
メソッドの結果を使っています。
import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; import { Observable, of } from 'rxjs'; import { map, tap, catchError } from 'rxjs/operators'; import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private router: Router, private auth: AuthService) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> { return this.auth.isAuthenticated().pipe( tap(loggedIn => { if (!loggedIn) { this.router.navigate(['/login']); } }) ); } }
PetService
API GatewayのAPIに対してリクエストして、ペット情報一覧を取得します。
$ ng g service pet/pet
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { environment } from '../../environments/environment'; import { Observable, of } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class PetService { private Url = environment.apiBaseUrl + '/pets'; constructor(private http: HttpClient) {} public getPets(token: string): Observable<any> { const httpOptions = { headers: { Authorization: token } }; return this.http.get<any>(this.Url, httpOptions).pipe( tap(users => users), catchError(this.handleError('getFile', [])) ); } private handleError<T>(operation = 'operation', result?: T) { return (error: any): Observable<T> => { console.error(error); // log to console instead this.log(`${operation} failed: ${error.message}`); return of(result as T); }; } private log(message: string) { console.log('petService: ' + message); } }
- getPets(token: string)
- APIリクエストをする処理を書いています。APIにはCognito認証設定がされているので、その際に必要となるトークンをヘッダーに付与してリクエストするようにしています。
HTTPリクエストの箇所は公式サイトのチュートリアルがとても参考になります。
私がはじめたころは見つけられなかったのですが、日本語のサイトもあるのでスムーズに勉強できるかと思います。翻訳してくださった方ほんと感謝です。
https://angular.jp/tutorial/toh-pt6
AppRoutingModule
ルーティングの定義を行います。
プロジェクト作成時に--routing
オプションを指定しているので、src/app/app-routing.module.ts
が作成されているかと思います。
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { LoginComponent } from './component/login/login.component'; import { SignupComponent } from './component/signup/signup.component'; import { HomeComponent } from './component/home/home.component'; import { PetComponent } from './component/pet/pet.component'; import { AuthGuard } from './auth/auth.guard'; const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, { path: 'home', component: HomeComponent, canActivate: [AuthGuard] }, { path: 'login', component: LoginComponent }, { path: 'signup', component: SignupComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {}
HomeComponent
、LoginComponent
、SignupComponent
の3つのページがあり、HomeComponent
はログインしたユーザーのみが閲覧できるようにするため、AuthGuard
でルーティングまえに検証するようにしています。ペット情報の取得と一覧表示を行うPetComponent
もありますが、こちらは、HomeComponent
の子コンポーネントとします。
※各ページのコンポーネントについては次のブログで紹介します。少々お待ちください。
さいごに
いかがだったでしょうか。
Angular6とAWS-Amplify
を使ってフロントエンド部分の設定周りと、Amazon Cognito、API Gatewayと通信するところのロジック部分の実装を行いました。
次は画面を構成するコンポーネント部分の実装やっていきます。